module d_cache #(parameter A_WIDTH = 32, parameter C_INDEX = 10)(
    input [A_WIDTH-1:0] p_a, // data_sram_addr(processed)
    input [31:0] p_dout, // data_sram_wdata
    output [31:0] p_din,  // readdata / data_sram_rdata_reg
    input p_strobe, // data_sram_en
    input [3:0] data_sram_wen,
    output p_ready, // m_d_ready
    input clk, clrn, // clk, resetn
    output [A_WIDTH-1:0] m_a, // to axi mem_a
    input [31:0] m_dout, // from axi mem_data
    output [31:0] m_din, // to axi mem_st_data
    output m_strobe, // to axi mem_access
    output m_rw, // to axi mem_write
    output [3:0] m_sel,
    input m_ready // from axi mem_ready
);
   //regs
    localparam T_WIDTH = A_WIDTH - C_INDEX - 2; // 1 block = 1 word
    reg valid0 [0 : (1 << C_INDEX) - 1];
    reg valid1 [0 : (1 << C_INDEX) - 1];
    reg dirty0 [0 : (1 << C_INDEX) - 1];
    reg dirty1 [0 : (1 << C_INDEX) - 1];
    reg [T_WIDTH-1:0] tags0[ 0: (1<<C_INDEX)-1];  // 2^C_INDEX - 1 T_WIDTH bits regs
    reg [T_WIDTH-1:0] tags1[ 0: (1<<C_INDEX)-1];
    reg [31:0] data0 [ 0: (1<<C_INDEX)-1]; // 2^C_INDEX - 1 32bits regs
    reg [31:0] data1 [ 0: (1<<C_INDEX)-1];
    reg LRUway [ 0: (1<<C_INDEX)-1];

    //control signals
    wire p_rw = (data_sram_wen==4'b0000) ? 0 : 1;
    wire nocache = (p_a[31:16]==16'h1faf) ? 1 : 0;
    wire [C_INDEX-1:0] index = p_a [C_INDEX+1:2] ;
    wire [T_WIDTH-1:0] tag = p_a[A_WIDTH-1:C_INDEX+2];
    wire way0_hit = valid0[index] & (tags0[index] == tag) & ~nocache;
    wire way1_hit = valid1[index] & (tags1[index] == tag) & ~nocache;
    wire cache_hit = way0_hit | way1_hit;
    wire cache_miss = ~cache_hit;
    wire sel_way0 = cache_hit & way0_hit | cache_miss & LRUway[index]==0;
    wire valid = sel_way0 ? valid0[index] : valid1[index];
    wire dirty = sel_way0 ? dirty0[index] : dirty1[index];
    wire writeback = p_strobe & cache_miss & valid & dirty & ~nocache;
    reg [1:0] state;
    parameter IDLE = 2'b00, WB = 2'b01, RM = 2'b10;
    wire c_write = (state == RM & m_ready & ~nocache) | (state == IDLE & cache_hit & p_rw);
    
    //datapath
    wire[T_WIDTH-1:0] tagout = sel_way0 ? tags0[index] : tags1[index];
    wire[31:0] c_dout = sel_way0 ? data0[index] : data1[index];
    assign p_din = cache_hit ? c_dout : m_dout;
    assign p_ready = cache_hit | (state == RM & m_ready) | (nocache & p_rw & m_ready);
    assign m_a = (state == WB) ? {tagout, index, 2'b00} : p_a;
    assign m_din = p_strobe & p_rw & nocache ? p_dout : c_dout;
    assign m_strobe = p_strobe &((state == WB | state == RM) | nocache);
    assign m_rw = p_strobe & (state == WB | (p_rw & nocache)); // write back
    assign m_sel = nocache ? data_sram_wen : 4'b1111;
    wire[31:0] hit_c_din = {
        data_sram_wen[3] ? p_dout[31:24] : c_dout[31:24],
        data_sram_wen[2] ? p_dout[23:16] : c_dout[23:16],
        data_sram_wen[1] ? p_dout[15:8] : c_dout[15:8],
        data_sram_wen[0] ? p_dout[7:0] : c_dout[7:0]
    };
    wire[31:0] miss_c_din = {
        data_sram_wen[3] ? p_dout[31:24] : m_dout[31:24],
        data_sram_wen[2] ? p_dout[23:16] : m_dout[23:16],
        data_sram_wen[1] ? p_dout[15:8] : m_dout[15:8],
        data_sram_wen[0] ? p_dout[7:0] : m_dout[7:0]
    };

    //state transitions
    always@(posedge clk) begin
        if(clrn == 0) begin
            state <= IDLE;
        end
        else if(p_strobe) begin
            case(state)
                IDLE: state <= writeback ? WB : 
                                        p_strobe & (cache_miss | (nocache & ~p_rw)) ? RM : IDLE;
                WB: state <= m_ready ? RM : WB;
                RM: state <= m_ready ? IDLE : RM;
            endcase
        end
    end

    always@(posedge clk) begin
        if (clrn == 0) begin
            valid0 <= '{default: '0};
            valid1 <= '{default: '0};
        end else if (c_write) begin
            if(sel_way0) begin //cache_hit & way0_hit | cache_miss & LRUway[index] == 0
                valid0[index] <= 1'b1;
                dirty0[index] <= p_rw;
                tags0[index] <= tag;
                data0[index] <= cache_hit ? hit_c_din : miss_c_din;
                LRUway[index] <= 1;
            end else begin //cache_hit & way1_hit | cache_miss & LRUway[index] == 1
                valid1[index] <= 1'b1;
                dirty1[index] <= p_rw;
                tags1[index] <= tag;
                data1[index] <= cache_hit ? hit_c_din : miss_c_din;
                LRUway[index] <= 0;
            end
        end else begin
            if(way0_hit) begin
                LRUway[index] <= 1;
            end else if(way1_hit) begin
                LRUway[index] <= 0;
            end else begin
                LRUway[index] <= LRUway[index];
            end
        end
    end

endmodule
